﻿using Newtonsoft.Json.Serialization;
using Sustainalytics.Admin.Synchronization.Utility;
using Sustainalytics.ESGRatings.DataContract;
using Sustainalytics.ESGRatings.DataContract.ESG;
using Sustainalytics.Utils.Enumerable;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace Sustainalytics.ESGRatings.BusinessLogic.ESGClient
{
    public class ESGClient : IDisposable
    {
        private readonly Uri esgComputeAddress;
        private readonly Uri esgLoadAddress;
        private readonly Uri CleanAddress;
        private readonly HttpClient client;
        private readonly JsonMediaTypeFormatter jsonMediaTypeFormatter;

        public ESGClient(string esgComputeUri)
        {
            Guard.ArgumentNotNull(esgComputeUri, "esgComputeUri");

            esgComputeAddress = new Uri(esgComputeUri + "/esgcompute");
            esgLoadAddress = new Uri(esgComputeUri + "/dataprovisioning/esgsets");
            CleanAddress = new Uri($"{esgComputeUri}/health/clean");
            client = new HttpClient();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            client.Timeout = new TimeSpan(0, 20, 0);
            jsonMediaTypeFormatter = new JsonMediaTypeFormatter
            {
                SerializerSettings = { ContractResolver = new CamelCasePropertyNamesContractResolver() }
            };
        }

        public async Task<ESGRatingsDto> GetRatings(ESGRatingsOptionsDto computeOptions)
        {
            Guard.ArgumentNotNull(computeOptions, "computeOptions");

            try
            {
                var result = await client.PostAsync(esgComputeAddress, computeOptions, jsonMediaTypeFormatter);
                if (result.IsSuccessStatusCode)
                {
                    var esgRatingsDto = await result.Content.ReadAsAsync<ESGRatingsDto>(jsonMediaTypeFormatter.Yield());
                    return esgRatingsDto;
                }

                var errorModel = await result.Content.ReadAsAsync<ErrorModel>(jsonMediaTypeFormatter.Yield());
                throw new ESGClientException
                {
                    ErrorStatusCode = errorModel?.ErrorStatusCode ?? 0,
                    ErrorMessage = errorModel != null ? errorModel.ErrorMessage : "",
                    HttpStatusCode = result.StatusCode,
                };
            }
            catch (HttpRequestException httpRequestException)
            {
                throw new ESGClientException("failed to get ratings", httpRequestException);
            }
        }

        public async Task Clean()
        {
            await client.PostAsync(CleanAddress, string.Empty, jsonMediaTypeFormatter).ConfigureAwait(false);
        }

        public async Task<Guid> LoadDataAsync(Guid setId)
        {
            var result = await client.GetAsync(esgLoadAddress).ConfigureAwait(false);

            if (result.IsSuccessStatusCode)
            {
                var availableSets = await result.Content
                    .ReadAsAsync<IEnumerable<ESGDataContainerDTO>>(new[] { jsonMediaTypeFormatter })
                    .ConfigureAwait(false);
                var esgDataContainerDtos = availableSets as ESGDataContainerDTO[] ?? availableSets.ToArray();
                if (esgDataContainerDtos.Any())
                {
                    if (setId != Guid.Empty && esgDataContainerDtos.Any(x => x.setId == setId))
                    {
                        var esgDataContainerDto = esgDataContainerDtos.FirstOrDefault(x => x.setId == setId);
                        if (esgDataContainerDto != null)
                            return esgDataContainerDto.setId;
                    }

                    if (setId == Guid.Empty && esgDataContainerDtos.Any())
                    {
                        var defaultSet = esgDataContainerDtos.First();
                        return defaultSet.setId;
                    }
                }
            }

            result = await client.PutAsync(esgLoadAddress, new ESGDataContainerDTO { setId = setId, isActive = false }, jsonMediaTypeFormatter).ConfigureAwait(false);

            if (result.IsSuccessStatusCode)
            {
                return await result.Content.ReadAsAsync<Guid>(new[] { jsonMediaTypeFormatter })
                    .ConfigureAwait(false);
            }

            return Guid.Empty;
        }

        public async Task<Guid> GetLatestActiveSet()
        {
            var result = await client.GetAsync(esgLoadAddress).ConfigureAwait(false);

            if (result.IsSuccessStatusCode)
            {
                var availableSets = await result.Content
                    .ReadAsAsync<IEnumerable<ESGDataContainerDTO>>(new[] { jsonMediaTypeFormatter })
                    .ConfigureAwait(false);
                if (availableSets != null && availableSets.Any())
                {
                    return availableSets.FirstOrDefault(x => x.isActive) != null ? availableSets.FirstOrDefault(x => x.isActive).setId : Guid.Empty;
                }
            }
            return Guid.Empty;
        }

        public async Task<IEnumerable<Guid>> GetSetIds()
        {
            var result = await client.GetAsync(esgLoadAddress).ConfigureAwait(false);

            if (result.IsSuccessStatusCode)
            {
                var availableSets = await result.Content
                    .ReadAsAsync<IEnumerable<ESGDataContainerDTO>>(new[] { jsonMediaTypeFormatter })
                    .ConfigureAwait(false);
                if (availableSets != null && availableSets.Any())
                    return availableSets.Select(x => x.setId);
            }

            return Enumerable.Empty<Guid>();
        }

        public void Dispose()
        {
            client?.Dispose();
        }
    }
}